import logging  # 导入logging模块，用于记录日志
import os  # 导入os模块，用于处理文件和目录
from zipfile import ZipFile  # 从zipfile模块导入ZipFile，用于处理zip文件

import cv2 as cv  # 导入cv2模块，用于图像处理
import numpy as np  # 导入numpy模块，用于处理大型多维数组和矩阵
import yaml  # 导入yaml模块，用于处理YAML文件
from skimage import io  # 从skimage模块导入io，用于图像的输入输出


# 这个函数用于对图像进行随机变换
# scale_min和scale_max用于设置缩放范围
# 函数返回处理后的图像

def random_transform(img, scale_min=0.5, scale_max=1.5):
    # 获取图像的高度和宽度
    h, w = img.shape[:2]

    # 1. 随机缩放图像
    # 生成一个随机的缩放因子，范围在scale_min和scale_max之间
    scale = np.random.uniform(scale_min, scale_max)
    # 计算缩放后的图像宽度和高度
    new_w, new_h = int(w * scale), int(h * scale)
    # 使用cv2.resize函数对图像进行缩放
    resized_img = cv.resize(img, (new_w, new_h), interpolation=cv.INTER_LINEAR)

    # 2. 从图像中心裁剪回原始大小
    # 计算裁剪的起始x坐标，确保裁剪后的图像宽度等于原始宽度
    start_x = (new_w - w) // 2
    # 计算裁剪的起始y坐标，确保裁剪后的图像高度等于原始高度
    start_y = (new_h - h) // 2
    # 从缩放后的图像中裁剪出原始大小的区域
    cropped_img = resized_img[start_y:start_y + h, start_x:start_x + w]

    # 3. 随机旋转图像，角度在-45°到45°之间
    # 生成一个随机的旋转角度，范围在-45°到45°之间，并转换为弧度
    rotation_angle = np.random.uniform(-45, 45) * np.pi / 180

    # 计算仿射变换矩阵
    # 使用cv2.getRotationMatrix2D获取旋转图像所需的旋转矩阵
    # center为旋转中心，rotation_angle为旋转角度，1.0为缩放因子
    center = (w / 2, h / 2)
    matrix = cv.getRotationMatrix2D(center, rotation_angle, 1.0)

    # 应用仿射变换
    # 使用cv2.warpAffine函数对裁剪后的图像应用仿射变换（旋转）
    # matrix为变换矩阵，(w, h)为输出图像的大小
    rotated_img = cv.warpAffine(cropped_img, matrix, (w, h))

    # 返回旋转后的图像
    return rotated_img

# 这个函数用于处理zip文件中的图像，可以设置缩放范围、生成图像数量、进度回调、线程事件和输出类型
# zip_path用于指定zip文件的路径
# scale_min和scale_max用于设置缩放范围
# num_images用于设置生成图像的数量
# progress_callback用于设置进度回调，传入的是 batch_transform_app.py 中的 update_progress_callback 函数
# thread_event用于设置线程事件
# output_type用于设置输出图像通道数，可以是RGB、Grayscale或Binary
def process_images_from_zip(zip_path, scale_min=0.5, scale_max=1.5, num_images=10, progress_callback=None, thread_event=None, output_type='RGB'):  
    
    # 如果没有，则创建 out 文件夹
    out_path = 'out/'  # 设置输出路径
    if not os.path.exists(out_path):  # 如果输出路径不存在
        logging.info('创建输出目录 %s', out_path)  # 记录日志，创建输出目录
        os.mkdir(out_path)  # 创建输出目录

    # 打开zip文件并获取其中的文件列表
    logging.info('开始处理zip文件中的图像')  # 记录日志，开始处理zip文件中的图像
    with ZipFile(zip_path, 'r') as zipped:  # 打开zip文件
        namelist = [name for name in zipped.namelist() if name.lower().endswith(('.png', '.jpg', '.jpeg', '.tiff', '.bmp', '.gif'))]  # 获取zip文件中所有图像的文件名
        length = len(namelist)  # 获取文件名列表的长度

        for name in namelist:  # 遍历文件名列表
            # 更新进度条
            if progress_callback:  # 如果有进度回调
                # 直接调用 batch_transform_app.py 中的 update_progress_callback 函数，实现对进度条的更新
                progress_callback(int(100 * (namelist.index(name) + 1) / length)) # 需要+1的原因是，namelist.index(name)的值是从0开始的，而进度条的值是从1开始的
            
            # 检查用户是否点击了取消按钮，如果点击了取消按钮，则退出当前函数
            if thread_event and thread_event.is_set():  # 如果用户点击了取消按钮
                logging.info('用户点击了取消按钮')  # 记录日志，用户点击了取消按钮
                return  # 返回
            
            with zipped.open(name) as file:  # 打开zip文件中的文件
                # 以output_type指示的方式读取图像
                if output_type == 'RGB':  # 如果输出类型为RGB
                    img = cv.imdecode(np.frombuffer(file.read(), np.uint8), 1)  # 以RGB方式读取图像
                elif output_type == 'Grayscale':  # 如果输出类型为灰度图
                    img = cv.imdecode(np.frombuffer(file.read(), np.uint8), 0)  # 以灰度图方式读取图像
                elif output_type == 'Binary':  # 如果输出类型为二值图
                    img = cv.imdecode(np.frombuffer(file.read(), np.uint8), 0)  # 读取图像
                    thresh_hold, img = cv.threshold(img, 0, 255, cv.THRESH_OTSU)  # 将图像转换为二值图
                else:  # 如果输出类型无效
                    raise ValueError('无效的输出类型: ' + output_type)  # 抛出异常
                
                new_img = cv.resize(img, (224, 224), interpolation=cv.INTER_NEAREST)  # 将图像大小调整为224x224
                # 224x224 这个尺寸是比较常用的，因为它很多神经网络的输入都是这个尺寸

                output_name = os.path.join(out_path, os.path.basename(name))  # 获取输出文件名
                logging.info(f'保存处理后的图像到: {output_name}')  # 记录日志，保存处理后的图像
                
                io.imsave(output_name, new_img)  # 保存处理后的图像
                # 之所以不使用cv2.imwrite函数，是因为cv2.imwrite对中文的路径支持不好，所以这里使用skimage.io.imsave函数

                # 为每张图像生成多张变换后的图像，生成的图像数量由num_images指定
                for i in range(num_images - 1):  # 遍历生成图像的数量
                    transformed_img = random_transform(new_img)  # 对图像进行随机变换
                    output_name_transformed = os.path.join(out_path, f"{os.path.splitext(os.path.basename(name))[0]}_transformed_{i}.jpg")  # 获取变换后的图像的文件名
                    io.imsave(output_name_transformed, transformed_img)  # 保存变换后的图像


# 这个函数用于从配置文件中加载配置
def load_config():
    # 从config.yaml文件中加载配置字典对象，并指定utf-8编码
    with open('config.yaml', 'r', encoding='utf-8') as f:
        config = yaml.safe_load(f)
        
    return config  # 返回配置字典对象